package com.ht.oss.dao;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.ht.oss.config.BucketsConfig;
import com.ht.oss.obj.Bucket;
import com.ht.oss.utils.GlobalThreadPool;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;

/**
 * 文件访问层
 */
@Component
public class OssDao {
    @Autowired
    BucketsConfig bucketsConfig;
    @Autowired
    GlobalThreadPool globalThreadPool;

    Map<String, Bucket> bucketMap;

    /**
     * 无类型的上传文件
     */
    public void upload(byte[] data, Bucket bucket, String md5) throws IOException {
        upload(data, bucket, md5, null);
    }

    /**
     * 上传文件
     * @param data 文件数据
     * @param bucket 文件目标 bucket
     * @param md5 文件 md5 用于确定文件名和文件路径
     * @param type 文件类型
     * @throws IOException 文件写入本地异常
     */
    public void upload(byte[] data, Bucket bucket, String md5, String type) throws IOException {
        // 按照文件md5来分两层文件路径
        char[] chars = md5.toCharArray();
        Path path = bucket
                .getRoot()
                .toPath()
                .resolve(String.valueOf(chars[0]))
                .resolve(String.valueOf(chars[1]));
        // 保证没有类型的文件被存储
        if (StrUtil.isNotBlank(type)) {
            path = path.resolve(md5 + '.' + type);
        } else {
            path = path.resolve(md5);
        }

        File file = path.toFile();
        file.getParentFile().mkdirs();
        try (
                ByteArrayInputStream is = new ByteArrayInputStream(data);
                FileOutputStream os = new FileOutputStream(file);
                ) {
            IoUtil.copy(is, os);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        bucket.getContent().put(md5, file); // 需要将新文件加入到容器中
    }

    public Bucket getBucketByName(String bucketName) {
        return bucketMap.get(bucketName);
    }

    /**********************      init    ********************************/

    /**
     * 初始化，从本地读出并创建所有bucket
     */
    @PostConstruct
    void init() {
        File bucketsRoot = new File(bucketsConfig.getBucketsRoot());
        checkCreateDir(bucketsRoot);
        bucketMap = new HashMap<>();
        // 独处所有配置的bucket，构建为bucketMap
        bucketsConfig.getBuckets().forEach((name) -> {
            File subDir = new File(bucketsRoot, name);
            checkCreateDir(subDir);
            Bucket bucket = new Bucket();
            bucket.setName(name);
            bucket.setRoot(subDir);
            Map<String, File> content = new HashMap<>();
            bucket.setContent(content);
            bucketMap.put(name, bucket);
            readFileToContent(bucket);
        });
    }

    /**
     * 读取bucket的 root，将文件读到 content 中
     * @param bucket 目标 bucket
     */
    void readFileToContent(Bucket bucket) {
        File root = bucket.getRoot();
        if (root == null) {
            return;
        }
        File[] dirList = root.listFiles(File::isDirectory);
        if (dirList == null) {
            return;
        }
        for (File dir: dirList) {
            globalThreadPool.execute(() -> {
                Queue<File> workQueue = new LinkedList<>();
                workQueue.add(dir);
                Map<String, File> content = bucket.getContent();
                while (!workQueue.isEmpty()) {
                    File f = workQueue.poll();
                    for (File file : Objects.requireNonNull(f.listFiles())) {
                        if (file.isFile()) {
                            String md5 = FileUtil.getPrefix(file);
                            content.put(md5, file);
                        } else if (file.isDirectory()) {
                            workQueue.add(file);
                        }
                    }
                }
            });
        }
    }

    /**
     * 检查并创建文件夹
     * @param dir 目标文件夹
     */
    void checkCreateDir(File dir) {
        if (dir.exists() && !dir.isDirectory()) {
            throw new RuntimeException("初始化失败，请检查本地文件");
        }
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new RuntimeException("初始化失败，请检查本地文件");
            }
        }
    }
}
